#!/usr/bin/env python

"""
Copyright (C) 2006-2009 Citrix Systems Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; version 2.1 only. with the special
exception on linking described in file LICENSE.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.
"""
from __future__ import print_function

import sys
import os
import commands
import time
import re
from optparse import OptionParser
#import XenAPI

pool_conf = '@ETCDIR@/pool.conf'
inventory_file = '@INVENTORY@'
management_conf = '/etc/firstboot.d/data/management.conf'
network_reset = '/tmp/network-reset'

def read_dict_file(fname):
	f = open(fname, 'r')
	d = {}
	for l in f.readlines():
		kv = l.split('=')
		d[kv[0].strip()] = kv[1].strip().strip("'")
	return d

def read_inventory():
	return read_dict_file(inventory_file)

def read_management_conf():
	return read_dict_file(management_conf)

def write_inventory(inventory):
	f = open(inventory_file, 'w')
	for k in inventory:
		f.write(k + "='" + inventory[k] + "'\n")
	f.flush()
	os.fsync(f.fileno())
	f.close()

def valid_vlan(vlan):
	if not re.match('^\d+$', vlan):
		return False
	if int(vlan)<0 or int(vlan)>4094:
		return False
	return True

if __name__ == "__main__":
	parser = OptionParser()
	parser.add_option("-m", "--master", help="Master's address", dest="address", default=None)
	parser.add_option("--device", help="Device name of new management interface", dest="device", default=None)
	parser.add_option("--mode", help='IP configuration mode for new management interface: "dhcp" or "static" (default is dhcp)', dest="mode", default="dhcp")
	parser.add_option("--novlan", help="no vlan is used for new management interface", dest="novlan", action="store_const", const=True, default=False)
	parser.add_option("--vlan", help="vlanID for new management interface to be on vlan network", dest="vlan", default=None)
	parser.add_option("--ip", help="IP address for new management interface", dest="ip", default='')
	parser.add_option("--netmask", help="Netmask for new management interface", dest="netmask", default='')
	parser.add_option("--gateway", help="Gateway for new management interface", dest="gateway", default='')
	parser.add_option("--dns", help="DNS server for new management interface", dest="dns", default='')
	(options, args) = parser.parse_args()
	
	# Determine pool role
	try:
		f = open(pool_conf, 'r')
		try:
			l = f.readline()
			ls = l.split(':')
			if ls[0].strip() == 'master':
				master = True
				address = 'localhost'
			else:
				master = False
				if options.address == None:
					address = ls[1].strip()
				else:
					address = options.address
		finally:
			f.close()
	except:
		pass
	
	# Get the management device from the firstboot data if not specified by the user
	if options.device == None:
		try:
			conf = read_management_conf()
			device = conf['LABEL']
		except:
			print("Could not figure out which interface should become the management interface. \
				Please specify one using the --device option.")
			sys.exit(1)
	else:
		device = options.device

	# Get the VLAN if provided in the firstboot data and not specified by the user
	vlan = None
	if options.vlan:
		if options.novlan:
			parser.error('"--vlan <vlanId>" and "--novlan" should not be used together')
			sys.exit(1)
		if not valid_vlan(options.vlan):
			print("VLAN tag you gave was invalid, It must be between 0 and 4094")
			sys.exit(1)
		vlan = options.vlan
	elif not options.novlan:
		try:
			conf = read_management_conf()
			vlan = conf['VLAN']
		except KeyError:
			pass

	# Determine IP configuration for management interface
	options.mode = options.mode.lower()
	if options.mode not in ["dhcp", "static"]:
		parser.error('mode should be either "dhcp" or "static"')
		sys.exit(1)
		
	if options.mode == 'static' and (options.ip == '' or options.netmask == ''):
		parser.error("if static IP mode is selected, an IP address and netmask need to be specified")
		sys.exit(1)
	
	# Warn user
	if not os.access('/tmp/fist_network_reset_no_warning', os.F_OK):
		configuration = []
		configuration.append("Management interface:   " + device)
		configuration.append("IP configuration mode:  " + options.mode)
		if vlan != None:
			configuration.append("Vlan:                   " + vlan)
		if options.mode == "static":
			configuration.append("IP address:             " + options.ip)
			configuration.append("Netmask:                " + options.netmask)
		if options.gateway != '':
			configuration.append("Gateway:                " + options.gateway)
		if options.dns != '':
			configuration.append("DNS server(s):          " + options.dns)
		if master == False:
			configuration.append("Pool master's address:  " + address)
		warning = """----------------------------------------------------------------------
!! WARNING !!

This command will reboot the host and reset its network configuration.
Any running VMs will be forcefully shutdown.

Before completing this command:
- Where possible, cleanly shutdown all VMs running on this host.
- Disable HA if this host is part of a resource pool with HA enabled.
----------------------------------------------------------------------

Your network will be re-configured as follows:\n\n"""
		confirmation = """\n\nIf you want to change any of the above settings, type 'no' and re-run
the command with appropriate arguments (use --help for a list of options).

Type 'yes' to continue.
Type 'no' to cancel.
"""
		res = raw_input(warning + '\n'.join(configuration) + confirmation)
		if res != 'yes':
			sys.exit(1)
	
	# Update master's IP, if needed and given
	if master == False and options.address != None:
		print("Setting master's ip (" + address + ")...")
		try:
			f = open(pool_conf, 'w')
			f.write('slave:' + address)
		finally:
			f.flush()
			os.fsync(f.fileno())
			f.close()

	# Construct bridge name for management interface based on convention
	if device[:3] == 'eth':
		bridge = 'xenbr' + device[3:]
	else:
		bridge = 'br' + device
	
	# Ensure xapi is not running
	print("Stopping xapi...")
	os.system('service xapi stop >/dev/null 2>/dev/null')
	
	# Reconfigure new management interface
	print("Reconfiguring " + device + "...")
	os.system('service xcp-network stop >/dev/null 2>/dev/null')
	try:
		os.remove('/var/lib/xcp/networkd.db')
	except Exception as e:
		print('Warning: Failed to delete networkd.db.\n%s' % e)

	# Update interfaces in inventory file
	print('Updating inventory file...')
	inventory = read_inventory()
	if vlan != None:
		inventory['MANAGEMENT_INTERFACE'] = 'xentemp'
	else:
		inventory['MANAGEMENT_INTERFACE'] = bridge
	inventory['CURRENT_INTERFACES'] = ''
	write_inventory(inventory)

	# Rewrite firstboot management.conf file, which will be picked it by xcp-networkd on restart (if used)
	try:
		f = file(management_conf, 'w')
		f.write("LABEL='" + device + "'\n")
		f.write("MODE='" + options.mode + "'\n")
		if vlan != None:
			f.write("VLAN='" + vlan + "'\n")
		if options.mode == 'static':
			f.write("IP='" + options.ip + "'\n")
			f.write("NETMASK='" + options.netmask + "'\n")
			if options.gateway != '':
				f.write("GATEWAY='" + options.gateway + "'\n")
			if options.dns != '':
				f.write("DNS='" + options.dns + "'\n")
	finally:
		f.flush()
		os.fsync(f.fileno())
		f.close()

	# Write trigger file for XAPI to continue the network reset on startup
	try:
		f = file(network_reset, 'w')
		f.write('DEVICE=' + device + '\n')
		f.write('MODE=' + options.mode + '\n')
		if vlan != None:
			f.write('VLAN=' + vlan + '\n')
		if options.mode == 'static':
			f.write('IP=' + options.ip + '\n')
			f.write('NETMASK=' + options.netmask + '\n')
			if options.gateway != '':
				f.write('GATEWAY=' + options.gateway + '\n')
			if options.dns != '':
				f.write('DNS=' + options.dns + '\n')
	finally:
		f.flush()
		os.fsync(f.fileno())
		f.close()

	# Reset the domain 0 network interface naming configuration
	# back to a fresh-install state for the currently-installed
	# hardware.
	os.system("/etc/sysconfig/network-scripts/interface-rename.py --reset-to-install")

	# Reboot
	os.system("mount -o remount,rw / && reboot -f")

